package com.contact.util;

import static org.junit.Assert.assertEquals;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.Date;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;



/**
 * Test basic object properties getter and setters using reflection
 * 
 */
public final class AutoTester { // NOPMD

    public static final String TEST_STRING = "HI";
    //public static final StatusCode TEST_STATUS_CODE = StatusCode.APPROVED;
    public static final int TEST_INTEGER = 42;
    public static final long TEST_LONG = 12358132134L;
    public static final double TEST_DOUBLE = 3.14159265;
    public static final float TEST_FLOAT = 3.14159265f;
    public static final byte TEST_BYTE = 0;
    public static final Date TEST_DATE = new Date();
    public static final LocalDate TEST_LOCALDATE = new LocalDate();
    public static final DateTime TEST_DATE_TIME = new DateTime();

    private AutoTester() {
        // Singleton
    }

    private static final Log LOG = LogFactory.getLog(AutoTester.class);

    /**
     * 
     * This calls a constructor of a class with no parameters even if it private
     * Use purely for Code Coverage
     * 
     * @param cls
     *            - class to call constructor on
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static Object genericTestConstructor(final Class<?> cls) {
        try {
            return cls.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(); // NOPMD
        }
    }

    public static <T> T populate(T bean) { // NOPMD
        PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(bean);
        for (PropertyDescriptor descriptor : descriptors) {
            Object testValue = null;
            if (!(descriptor.getName().equals("id") || descriptor.getName().equals("createdByUserId"))) {
                if (descriptor.getPropertyType().isAssignableFrom(String.class)) {
                    testValue = TEST_STRING;
                } else if (descriptor.getPropertyType().isAssignableFrom(Integer.TYPE)) {
                    testValue = TEST_INTEGER;
                } else if (descriptor.getPropertyType().isAssignableFrom(Long.TYPE)) {
                    testValue = TEST_LONG;
                } else if (descriptor.getPropertyType().isAssignableFrom(Date.class)) {
                    testValue = TEST_DATE;
                } else if (descriptor.getPropertyType().isAssignableFrom(LocalDate.class)) {
                    testValue = TEST_LOCALDATE;
                } else if (descriptor.getPropertyType().isAssignableFrom(Double.TYPE)) {
                    testValue = TEST_DOUBLE;
                } else if (descriptor.getPropertyType().isAssignableFrom(Byte.TYPE)) {
                    testValue = TEST_BYTE;
                } else if (descriptor.getPropertyType().isAssignableFrom(Float.TYPE)) {
                    testValue = TEST_FLOAT;
                } else if (descriptor.getPropertyType().isAssignableFrom(BigDecimal.class)) {
                    testValue = BigDecimal.ONE;
                } else if (descriptor.getPropertyType().isAssignableFrom(Boolean.TYPE)) {
                    testValue = true;
                } else if (descriptor.getPropertyType().isAssignableFrom(DateTime.class)) {
                    testValue = TEST_DATE_TIME; 
                }else {
                    LOG.debug("Skipping test value for " + descriptor.getName());
                }
                if (testValue != null) {
                    LOG.debug("testing " + descriptor.getName());
                    try {
                        PropertyUtils.setProperty(bean, descriptor.getName(), testValue);

                    } catch (NoSuchMethodException nsme) {
                        LOG.info(String.format("No setter or getter for property %s. Skipping...", descriptor.getName()));
                        continue;
                    } catch (IllegalAccessException e) {
                        LOG.info(String.format("Cannot access getter or setter for property %s. Skipping...",
                                descriptor.getName()));
                        continue;
                    } catch (InvocationTargetException e) {
                        LOG.info(String.format("General exeception for getter or setter for property %s. Skipping...",
                                descriptor.getName()));
                        continue;

                    }
                }// end if
            }
        }// end loop
        return bean;
    }// end populate

    @SuppressWarnings("unchecked")
    public static <T> void testGettersAndSetters(T bean) throws IllegalAccessException, InvocationTargetException { // NOPMD

        // test the getters and setters
        PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(bean);
        for (PropertyDescriptor descriptor : descriptors) {

            Class<?> propertyType = descriptor.getPropertyType();

            if (propertyType.isAssignableFrom(Class.class)) {
                continue;
            }

            Object testValue = null;
            if (propertyType.isAssignableFrom(String.class)) {
                testValue = TEST_STRING;
            } else if (propertyType.isAssignableFrom(Integer.TYPE)) {
                testValue = TEST_INTEGER;
            } else if (propertyType.isAssignableFrom(Long.TYPE)) {
                testValue = TEST_LONG;
            } else if (propertyType.isAssignableFrom(Date.class)) {
                testValue = TEST_DATE;
            } else if (propertyType.isAssignableFrom(LocalDate.class)) {
                testValue = TEST_LOCALDATE;
            } else if (propertyType.isAssignableFrom(Double.TYPE)) {
                testValue = TEST_DOUBLE;
            } else if (propertyType.isAssignableFrom(Byte.TYPE)) {
                testValue = TEST_BYTE;
            } else if (propertyType.isAssignableFrom(BigDecimal.class)) {
                testValue = BigDecimal.ONE;
            } else if (propertyType.isAssignableFrom(Boolean.TYPE)) {
                testValue = true;
            } else if (propertyType.isAssignableFrom(Float.TYPE)) {
                testValue = TEST_FLOAT;
            } else {
                // try constructing a value
                try {
                    testValue = genericTestConstructor(propertyType);
                } catch (Exception e) {
                    LOG.debug("Failed to construct test value for " + descriptor.getName());
                    testValue = null; // oh well
                }
            }
            // if (testValue != null) {
            LOG.debug("testing " + descriptor.getName());
            try {
                PropertyUtils.setProperty(bean, descriptor.getName(), testValue);
            } catch (NoSuchMethodException nsme) {
                LOG.info(String.format("No setter or getter for property %s. Skipping...", descriptor.getName()));
                continue;
            }
            try {
                Object value = PropertyUtils.getProperty(bean, descriptor.getName());
                assertEquals(testValue, value);
            } catch (NoSuchMethodException nsme) {
                LOG.info(String.format("No getter for property %s. Skipping...", descriptor.getName()));
                continue;
            }
        }

        // test general bean methods
        try {
            T other = (T) bean.getClass().newInstance();
            bean.equals(other);
            bean.equals(null); // NOPMD test null check
            bean.equals("empty"); // NOPMD test not matching class check
            T thisBean = bean;
            bean.equals(thisBean);
            if (bean instanceof Comparable) {
                ((Comparable<T>) bean).compareTo(other);
            }

        } catch (Exception ie) { // NOPMD
            // can't do it
        }

        try {
            bean.hashCode();
            bean.toString();
        } catch (Exception e) { // NOPMD
            // probably null sensitive
        }
    }

}
